using System;
using UnityEngine;
using System.Collections;


public class Maze: System.Object {
	private float cellSize = 60.0f;
	private ArrayList exits = new ArrayList();
	private int height;
	private ArrayList lights = new ArrayList();
	private MazeSquare[] map;
	private MazeMesh mazeMesh = new MazeMesh();
	private ArrayList mazeSquares = null;
	private int width;

	public void changeSquareColor(MazeLocation mazeLocation, Color color) {
		mazeMesh.changeSquareColor(mazeLocation, color);
	}

	public void createMaze(int numOfRooms, int width, int height) {
		this.width = width;
		this.height = height;

		exits = new ArrayList();

		map = new MazeSquare[width*height];

		// Make maze solid
		for (int i = 0; i < width*height; i++)
		{
			map[i] = new MazeSquare(true);
		}

		MazeLocation preLocation = new MazeLocation(-1, -1);

		for (int i = 0; i < numOfRooms; i++)
		{
			ArrayList area;
			int tryRadius;
			MazeLocation tryLocation;

			do
			{
				tryLocation = new MazeLocation((int)(UnityEngine.Random.value * width), (int)(UnityEngine.Random.value * height));
				tryRadius = (int)(3 + UnityEngine.Random.value * 3);
				area = getCircleArea(tryLocation, tryRadius);
			}while(!isAreaSolid(area));


			carveArea(getCircleArea(tryLocation,tryRadius-1));
			//Randomly put energy in rooms
			if (UnityEngine.Random.value > 0.4)
			{
				getMazeSquare(tryLocation).setEnergy(true);
			}

			if ( i == numOfRooms-1 )
			{
				Vector3 worldPos = new Vector3(tryLocation.x*cellSize, cellSize*2, tryLocation.z*cellSize);
				
				/* // wanted to have text object above the finish but couldnt figure out how to do it procedurally.
				GameObject goalTextObject = new GameObject("goalTextObject");
				
				goalTextObject.AddComponent<TextMesh>();
				goalTextObject.GetComponent<TextMesh>().text = "Finish";
				goalTextObject.GetComponent<TextMesh>().fontSize = 500;
				
				//goalTextObject.renderer.material.color = Color.blue;
				
				goalTextObject.AddComponent<MeshRenderer>();
				goalTextObject.GetComponent<MeshRenderer>().material =  
				//material.color = Color.red
				
				MonoBehaviour.print(goalTextObject.GetComponent<MeshRenderer>().material.ToString());
				
				//goalTextObject.GetComponent<MeshRenderer>().material = Resources.Load("Arial", Material);
				
				//myTextObject.renderer.material = myMaterial;
				
				goalTextObject.transform.position = worldPos;
				*/
				
				setFinishArea(getCircleArea(tryLocation, tryRadius-1));
				addLight(worldPos, cellSize*tryRadius, 3.4f, Color.cyan);
			}
			
			/* //No Lava - path finding is well demonstrated by the herder evade and maze walls
			else if (UnityEngine.Random.value > 0.8)
			{
				setLavaArea(getCircleArea(tryLocation,tryRadius-1));
				addLight(new Vector3(tryLocation.x*cellSize, cellSize*2, tryLocation.z*cellSize), cellSize*tryRadius, 3.4f, Color.red);
			}
			*/ 

			if (preLocation.x >= 0)
			{
				if (UnityEngine.Random.value < 0.4)
				{
					getMazeSquare(tryLocation).setHerderSpawnPoint(true);
				}
				carveArea(getLineArea(preLocation, tryLocation, 3.0f));
			}else{
				//Set first room to be spawn area
				setSpawnArea(getCircleArea(tryLocation,tryRadius-1));
			}
			preLocation = tryLocation;
		}

		for (int j = 0; j < height; j++)
		{
			for (int i = 0; i < width; i++)
			{
				MazeSquare mazeSquare = getMazeSquare(new MazeLocation(i,j));
				if (!mazeSquare.isSolid())
				{
					if (UnityEngine.Random.value < 0.0005f)
						mazeSquare.setStar(true);
				}

			}
		}

		for (int j = 0; j < getHeight(); j++)
		{
			for (int i = 0; i < getWidth(); i++)
			{
				MazeSquare mazeSquare = getMazeSquare(new MazeLocation(i,j));
				if (mazeSquare.isFinish())
					exits.Add(getMazePosition(new MazeLocation(i,j)));
			}
		}

		mazeMesh.fillMeshSquares(this, cellSize, map, width, height);
		return;
	}

	public float getCellSize() {
		return cellSize;
	}

	public ArrayList getCircleArea(MazeLocation center, float radius) {
		ArrayList area = new ArrayList();
		for (int j = center.x-(int)radius; j < center.x+(int)radius; j++)
		{
			for (int i = center.z-(int)radius; i < center.z+(int)radius; i++)
			{
				MazeLocation currPoint = new MazeLocation(j,i);
				if (Vector2.Distance(new Vector2(center.x, center.z), new Vector2(currPoint.x, currPoint.z)) < radius)
				{
					if (currPoint.x >=0 && currPoint.x < getWidth() & currPoint.z >= 0 && currPoint.z < getHeight())
						area.Add(currPoint);
				}
			}
		}
		return area;
	}

	public Vector3 getClosestSafePos(Vector3 target) {
		//if this is a valid point so return the world position
		MazeLocation centerCoord = getMazeCoord(target);
		if(bValidMazeSquareAtCoord(centerCoord))
			return target;

		//the requested position is not safe so we should find a new one
		//we will walk a circle around the cell - using a state machine

		for(int r = 1; r < 10; r++)
		{
			for (int x = centerCoord.x-r; x <= centerCoord.x+r; x++)
			{
				for (int y = centerCoord.z-r; y <= centerCoord.z+r; y++)
				{
					MazeLocation location = new MazeLocation(x,y);
					//dont bother checking the
					if(location == centerCoord)
					{
						//ifValid return world pos
						if(bValidMazeSquareAtCoord(location))
							return getMazePosition(location);
					}
				}
			}
		}

		//couldnt find a safe point
		return new Vector3(0,0,0);
	}

	public ArrayList getExits() {
		return exits;
	}

	public int getHeight() {
		return height;
	}

	public MazeLocation getMazeCoord(Vector3 position) {
		int x,z;
		if (position.x < 0 || position.x > cellSize * getWidth() + cellSize)
			x = 0;
		else
			x = (int)(position.x / cellSize);

		if (position.z < 0 || position.z > cellSize * getHeight() + cellSize)
			z = 0;
		else
			z = (int)(position.z / cellSize);

		return new MazeLocation(x,z);
	}

	public Vector3 getMazePosition(MazeLocation location) {
		return new Vector3((float)location.x*cellSize, cellSize/2,(float)location.z * cellSize);
	}

	public MazeSquare getMazeSquare(MazeLocation location) {
		if (location.x >= width || location.x < 0 || location.z >= height || location.z < 0)
			return new MazeSquare(true);
		return (MazeSquare)map[location.x+location.z*width];
	}

	public int getMazeX(Vector3 position) {
		if (position.x < 0 || position.x > cellSize * getWidth() + cellSize)
		{
			return 0;
		}
		return (int)(position.x / cellSize);
	}

	public int getMazeZ(Vector3 position) {
		if (position.z < 0 || position.z > cellSize * getHeight() + cellSize)
		{
			return 0;
		}
		return (int)(position.z / cellSize);
	}

	public Vector3 getRandomPositionOnSquare(MazeLocation mazeLocation) {
		Vector3 position = getMazePosition(mazeLocation);
		position.x += (UnityEngine.Random.value-0.5f)*cellSize;
		position.z += (UnityEngine.Random.value-0.5f)*cellSize;
		return position;
	}

	public ArrayList getSpawnArea() {
		ArrayList area = new ArrayList();
		for (int j = 0; j < height; j++)
		{
			for (int i = 0; i < width; i++)
			{
				MazeLocation location = new MazeLocation(i, j);
				if (getMazeSquare(location).isSpawnPoint())
				{
					area.Add(location);
				}
			}
		}

		return area;
	}

	public int getWidth() {
		return width;
	}

	public void resetSquareColors() {
		mazeMesh.resetSquareColors();
	}

	private void addLight(Vector3 position, float distance, float intensity, Color color) {
		GameObject newLight = new GameObject("Maze Light");
		newLight.transform.position = position;
		newLight.AddComponent("Light");
		newLight.light.color = color;
		newLight.light.range = distance;
		newLight.light.intensity = intensity;
		lights.Add( newLight );
	}

	bool bValidMazeSquareAtCoord(MazeLocation coord) {
		MazeSquare curMazeSquare = getMazeSquare(coord);
		if(!curMazeSquare.isSolid())
		{
			//this is a valid point so return the world position
			return true;
		}

		return false;
	}

	private void carveArea(ArrayList points) {
		foreach (MazeLocation point in points)
		{
			if (point.x > 0 && point.x < width &&
				point.z > 0 && point.z < height)
				getMazeSquare(point).carve();
		}
	}

	private void destroyMaze() {
		if (mazeSquares	== null)
				return;

		/*foreach (MazeSquare square in mazeSquares)
		{
			square.destroy();
		}*/
		mazeSquares.Clear();
	}

	private void fillArea(ArrayList points) {
		foreach (MazeLocation point in points)
		{
			if (point.x > 0 && point.x < width &&
				point.z > 0 && point.z <height)
				getMazeSquare(point).fill();
		}
	}

	private ArrayList getLineArea(MazeLocation start, MazeLocation end, float thickness) {
		ArrayList area = new ArrayList();

		// This method was adapted from
		// http://www.brackeen.com/vga/source/djgpp20/lines.c.html

		int dx,dy,sdx,sdy,px,py,dxabs,dyabs,i;
		float slope;

		dx=end.x-start.x;      /* the horizontal distance of the line */
		dy=end.z-start.z;      /* the vertical distance of the line */
		dxabs=Math.Abs(dx);
		dyabs=Math.Abs(dy);
		if(dx < 0)sdx=-1;else sdx = 1;
		if(dy < 0)sdy=-1;else sdy = 1;
		if (dxabs>=dyabs) /* the line is more horizontal than vertical */
		{
			slope=(float)dy / (float)dx;
			for(i=0;i!=dx;i+=sdx)
			{
				px=i+start.x;
				py=(int)(slope*(float)i)+start.z;
				area.AddRange(getCircleArea(new MazeLocation(px,py),(int)thickness));
			}
		}
		else /* the line is more vertical than horizontal */
		{
			slope=(float)dx / (float)dy;
			for(i=0;i!=dy;i+=sdy)
			{
				px=(int)(slope*(float)i)+start.x;
				py=i+start.z;
				area.AddRange(getCircleArea(new MazeLocation(px,py),(int)thickness));
			}
		}
		return area;
	}

	private int getMapIndex(MazeLocation location) {
		return (location.x + location.z*width);
	}

	private bool isAreaSolid(ArrayList points) {
		foreach (MazeLocation point in points)
		{
			if (point.x > 0 && point.x < width &&
				point.z > 0 && point.z <height)
			{
				if (!getMazeSquare(point).isSolid())
				{
					return false;
				}
			}else
			{
				return false;
			}
		}
		return true;
	}

	private void setFinishArea(ArrayList points) {
		foreach (MazeLocation point in points)
		{
			getMazeSquare(point).setFinish(true);
		}
	}

	private void setLavaArea(ArrayList points) {
		foreach (MazeLocation point in points)
		{
			getMazeSquare(point).setLava(true);
		}
	}

	private void setSpawnArea(ArrayList points) {
		foreach (MazeLocation point in points)
		{
			getMazeSquare(point).setSpawnPoint(true);
		}
	}

	private void print(string asdf)
	{
		MonoBehaviour.print(asdf);	
	}	
}

